﻿using Devil;
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Profiling;

namespace GameToolkit
{
    public interface INavMeshSource : IVolumetric
    {
        //Bounds localBounds { get; }
        bool AffectOnAgent(int agentId);
        GameObject gameObject { get; }
        Transform transform { get; }
        //NavMeshBuildSource GetBuildSource(ICollection<NavMeshBuildSource> sources);
        void GetBuildSource(int defaultAreaType, ICollection<NavMeshBuildSource> sources);
        //void GetBuildSource(ref List<NavMeshBuildSource> source);
    }

    [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
    public class NavAreaTypeAttribute : PropertyAttribute
    {
    }

    [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
    public class NavAgentTypeAttribute : PropertyAttribute
    {
    }

    [ExecuteInEditMode]
    [AddComponentMenu("My Navigation/RuntimeNavMeshBuilder")]
    public class RuntimeNavMeshBuilder : VolumetricBounds, INavMeshSource
    {
#pragma warning disable CS0649
        public LayerMask m_ConsiderLayers = -1;

        [NavAgentType]
        [SerializeField]
        int m_AgentTypeID;

        [NavAreaType]
        [SerializeField]
        int m_ModifyWalkableArea;

        //BuildTask mOperation;
        bool mRebuild;
        NavMeshData m_NavMesh;
        List<NavMeshBuildSource> m_Sources = new List<NavMeshBuildSource>();
        NavMeshDataInstance m_Instance;
#pragma warning restore
        HashSet<int> mIncludeSrcs;
        BuildCmd mCmd;
        ParallelDispatcher.Handler mCmdHandler;

        public bool AffectOnAgent(int agentId)
        {
            return true;
        }

        public void GetBuildSource(int areaType, ICollection<NavMeshBuildSource> sources)
        {
            var mcenter = transform.TransformPoint(m_BoundsCenter);
            var scale = transform.lossyScale;
            var msize = new Vector3(m_BoundsSize.x * Mathf.Abs(scale.x), m_BoundsSize.y * Mathf.Abs(scale.y), m_BoundsSize.z * Mathf.Abs(scale.z));

            var src = new NavMeshBuildSource();
            src.shape = NavMeshBuildSourceShape.ModifierBox;
            src.transform = Matrix4x4.TRS(mcenter, transform.rotation, Vector3.one);
            src.size = msize;
            src.area = 1;
            sources.Add(src);
        }


        public bool UseBuildSource(INavMeshSource source)
        {
            if( (m_ConsiderLayers & (1 << source.gameObject.layer)) != 0 && source.AffectOnAgent(m_AgentTypeID))
            {
                var bounds = source.localBounds;
                bounds = CalcuateBounds(transform.worldToLocalMatrix * source.localToWorldMatrix, bounds);
                return localBounds.Intersects(bounds);
            }
            else
            {
                return false;
            }
        }

//        private IEnumerator BuildNavMeshData()
//        {
//            //mOperation.IsAsyncDone = false;
//            yield return null;
//#if UNITY_EDITOR
//            Profiler.BeginSample("Collect Nav Source");
//#endif
//            m_Sources.Clear();
//            mIncludeSrcs.Clear();
//            //int walkable = NavMesh.GetAreaFromName("Walkable");

//            lock (sLock)
//            {
//                foreach (var src in sBuildSource)// (int i = 0; i < sBuildSource.Count; i++)
//                {
//                    //var src = sBuildSource[i];
//                    if (UseBuildSource(src))
//                    {
//                        mIncludeSrcs.Add(src.GetHashCode());
//                        src.GetBuildSource(m_ModifyWalkableArea, m_Sources);
//                        //if (nav.area == walkable && m_ModifyWalkableArea != 0)
//                        //    nav.area = m_ModifyWalkableArea;
//                        //m_Sources.Add(nav);
//                    }
//                }
//            }
//#if UNITY_EDITOR
//            Profiler.EndSample();
//#endif
//            yield return null;
//            //AppendModifierVolumes(ref m_Sources);
//            //yield return null;
//            if (m_NavMesh == null)
//            {
//                m_NavMesh = new NavMeshData();
//                m_NavMesh.position = transform.position;
//                m_NavMesh.rotation = transform.rotation;
//                m_Instance = NavMesh.AddNavMeshData(m_NavMesh);
//                m_Instance.owner = this;
//                yield return null;
//            }
//            var setting = NavMesh.GetSettingsByID(m_AgentTypeID);
//            var bounds = new Bounds(m_BoundsCenter, m_BoundsSize);
//            var oper = NavMeshBuilder.UpdateNavMeshDataAsync(m_NavMesh, setting, m_Sources, bounds);
//            //mOperation.Operation = oper;
//            yield return oper;
//            //mOperation.IsAsyncDone = true;
//            //mOperation.Operation = null;
//#if UNITY_EDITOR
//            RTLog.Log(LogCat.AI, gameObject, "NavMesh Data Updated.");
//#endif
//        }

        private void OnEnable()
        {
            //if (mOperation == null)
            //    mOperation = new BuildTask(this);
            if (mCmd == null)
                mCmd = new BuildCmd(this);
            if (mIncludeSrcs == null)
                mIncludeSrcs = new HashSet<int>();
            lock (sLock)
            {
                if (!sBuilders.Contains(this))// GetBuilder(m_AgentTypeID) == null)
                {
                    sBuilders.Add(this);

#if UNITY_EDITOR
                    if (Application.isPlaying)
                    {
#endif
                        mRebuild = true;
                        //mOperation.IsAsyncDone = false;
                        //if (SceneHelper.Instance != null && SceneHelper.Instance.IsLoading)
                        //    SceneHelper.Instance.AddAdditiveTask(ESceneEvent.WhileLoading, mOperation);
#if UNITY_EDITOR
                    }
#endif
                }
#if UNITY_EDITOR
                else
                {
                    Debug.LogError(string.Format("There has already exist a RuntimeNavMeshBuilder's instance for agent \"{0}\", \"{1}\" will not be used.",
                        NavMesh.GetSettingsNameFromID(m_AgentTypeID), name), gameObject);
                }
#endif
            }
        }

        private void OnDisable()
        {
            mCmdHandler.Abort();
            //mOperation.IsAsyncDone = true;
            lock (sLock)
            {
                if (sBuilders.Remove(this))
                {
#if UNITY_EDITOR
                    if (Application.isPlaying)
                    {
#endif
                        m_Instance.Remove();
                        m_Instance = default(NavMeshDataInstance);

                        if (m_NavMesh != null)
                        {
                            NavMeshBuilder.Cancel(m_NavMesh);
                            Destroy(m_NavMesh);
                            m_NavMesh = null;
                        }
#if UNITY_EDITOR
                    }
#endif
                }
            }
        }

        private void LateUpdate()
        {
            if (mRebuild
#if UNITY_EDITOR
                && Application.isPlaying
#endif
                )
            {
                mRebuild = false;
                //if (!mOperation.IsAsyncDone)
                {
                    mCmdHandler.Abort();
                    //StopCoroutine("BuildNavMeshData");
                    if (m_NavMesh != null)
                        NavMeshBuilder.Cancel(m_NavMesh);
                }
                //StartCoroutine("BuildNavMeshData");
                mCmdHandler = mCmd.Schedule(true);
#if UNITY_EDITOR
                mCmdHandler.SampleExecuteTime("NavMesh");
#endif
            }
        }

        class BuildCmd : IYieldCommand
        {
            RuntimeNavMeshBuilder mBuilder;

            internal BuildCmd(RuntimeNavMeshBuilder builder)
            {
                mBuilder = builder;
            }

            IEnumerator IYieldCommand.Execute()
            {
#if UNITY_EDITOR
                Profiler.BeginSample("Collect Nav Source");
#endif
                mBuilder.m_Sources.Clear();
                mBuilder.mIncludeSrcs.Clear();
                //int walkable = NavMesh.GetAreaFromName("Walkable");

                lock (sLock)
                {
                    foreach (var src in sBuildSource)// (int i = 0; i < sBuildSource.Count; i++)
                    {
                        //var src = sBuildSource[i];
                        if (mBuilder.UseBuildSource(src))
                        {
                            mBuilder.mIncludeSrcs.Add(src.GetHashCode());
                            src.GetBuildSource(mBuilder.m_ModifyWalkableArea, mBuilder.m_Sources);
                            //if (nav.area == walkable && m_ModifyWalkableArea != 0)
                            //    nav.area = m_ModifyWalkableArea;
                            //m_Sources.Add(nav);
                        }
                    }
                }
#if UNITY_EDITOR
                Profiler.EndSample();
#endif
                yield return null;
                //AppendModifierVolumes(ref m_Sources);
                //yield return null;
                if (mBuilder.m_NavMesh == null)
                {
                    mBuilder.m_NavMesh = new NavMeshData();
                    mBuilder.m_NavMesh.position = mBuilder.transform.position;
                    mBuilder.m_NavMesh.rotation = mBuilder.transform.rotation;
                    mBuilder.m_Instance = NavMesh.AddNavMeshData(mBuilder.m_NavMesh);
                    mBuilder.m_Instance.owner = mBuilder;
                    yield return null;
                }
                var setting = NavMesh.GetSettingsByID(mBuilder.m_AgentTypeID);
                var bounds = new Bounds(mBuilder.m_BoundsCenter, mBuilder.m_BoundsSize);
                var oper = NavMeshBuilder.UpdateNavMeshDataAsync(mBuilder.m_NavMesh, setting, mBuilder.m_Sources, bounds);
                //mOperation.Operation = oper;
                yield return new UnityInstruction(oper);
                //mOperation.IsAsyncDone = true;
                //mOperation.Operation = null;
#if UNITY_EDITOR
                RTLog.Log(LogCat.AI, mBuilder.gameObject, "NavMesh Data Updated.");
#endif
            }

            public override string ToString()
            {
                return "NavMeshBuilder";
            }
        }

        static object sLock = new object();
        static HashSet<INavMeshSource> sBuildSource = new HashSet<INavMeshSource>();
        static List<RuntimeNavMeshBuilder> sBuilders = new List<RuntimeNavMeshBuilder>();
        public static bool IsBuildingNavMeshData
        {
            get
            {
                lock (sLock)
                {
                    for (int i = 0; i < sBuilders.Count; i++)
                    {
                        if (sBuilders[i].mCmdHandler.IsAlive)
                            return true;
                    }
                }
                return false;
            }
        }
        public static void GetBuildingHandlers(ref ParallelDispatcher.CombinedHandler combination)
        {
            lock (sLock)
            {
                if (sBuilders.Count == 0)
                    return;
                if (combination.IsNull)
                    combination = new ParallelDispatcher.CombinedHandler("Runtime NavMeshBuilder");
                for (int i = 0; i < sBuilders.Count; i++)
                {
                    combination.Combine(sBuilders[i].mCmdHandler);
                }
            }
        }
        //static RuntimeNavMeshBuilder GetBuilder(int agentId)
        //{
        //    for (int i = 0; i < sBuilders.Count; i++)
        //    {
        //        if (sBuilders[i].m_AgentTypeID == agentId)
        //            return sBuilders[i];
        //    }
        //    return null;
        //}

        public static void RebuildAll()
        {
            lock (sLock)
            {
                for (int i = 0; i < sBuilders.Count; i++)
                {
                    var builder = sBuilders[i];
                    builder.mRebuild = true;
                }
            }
        }

        public static void SetDirty(INavMeshSource src)
        {
            lock (sLock)
            {
                if (sBuildSource.Contains(src))
                {
                    var id = src.GetHashCode();
                    for (int i = 0; i < sBuilders.Count; i++)
                    {
                        var builder = sBuilders[i];
                        if (builder.mIncludeSrcs.Contains(id) || builder.UseBuildSource(src))
                        {
                            builder.mRebuild = true;
                        }
                    }
                }
            }
        }

        public static void AddBuildSource(INavMeshSource src)
        {
            lock (sLock)
            {
                if (sBuildSource.Add(src))
                {
                    //var id = src.GetHashCode();
                    //sBuildSource.Add(src);
                    for (int i = 0; i < sBuilders.Count; i++)
                    {
                        var builder = sBuilders[i];
                        if (builder.UseBuildSource(src))
                        {
                            builder.mRebuild = true;
                        }
                    }
                }
            }
        }

        public static void RemoveBuildSource(INavMeshSource src)
        {
            lock (sLock)
            {
                if (sBuildSource.Remove(src))
                {
                    var id = src.GetHashCode();
                    for (int i = 0; i < sBuilders.Count; i++)
                    {
                        var builder = sBuilders[i];
                        if (builder.mIncludeSrcs.Remove(id))
                        {
                            builder.mRebuild = true;
                        }
                    }
                }
            }
        }

        public static IEnumerator<RuntimeNavMeshBuilder> GetBuildersEnumerator()
        {
            return sBuilders.GetEnumerator();
        }

#if UNITY_EDITOR


        public override Color color { get { return UnityEditor.Selection.activeGameObject == gameObject ? MathExt.DecodeRGB(~m_AgentTypeID ^ 0x088ff8) : Color.gray * 0.7f; } }
        //protected override bool DrawGizmos { get { return false; } }

        protected virtual void OnDrawGizmos()
        {
            Gizmos.DrawIcon(transform.position + Vector3.up * HandleUtility.GetHandleSize(transform.position) * 0.3f, "NavMeshData Icon");
#if !UNITY_2022_3_OR_NEWER
            if (UnityEditor.AI.NavMeshVisualizationSettings.showNavigation > 0)
            {
                Gizmos.matrix = transform.localToWorldMatrix;
                if (IsEditMode)
                    GizmosUtil.MarkAxisSystem(GizmosUtil.FactorFromSceneViewPixel(transform, Vector3.zero) * 30);
                Gizmos.color = color;
                Gizmos.DrawWireCube(m_BoundsCenter, m_BoundsSize);
            }
#endif
        }
#endif
        }
}